export enum codeEnum {
  TYPE_ERROR = 0, //0 图片文件类型不正确
  SIZE_EXCEEDS = 1, // 图片大小超出
  COMPRESSION = 2, //图片经过压缩
  UNCOMPRESSED = 3 // 图片未压缩
}

export enum fileType {
  BASE64 = 'base64',
  FILE = 'file'
}

export interface Option {
  maxSize?: `${string}M`
  targetSize?: `${string}M`
  targetWidth?: `${string}px` | number
  targrtHeight?: `${string}px` | number
  fileType?: fileType
}

export interface preUploadOption {
  file: File
  limitType?: Array<string>
  limitSize?: `${string}M`
  option?: Option
}

export interface preUpload {
  file: File | string
  fileName: string
  fileType: string
  size: string
  code: codeEnum
}

// file -> base64
const changeFileToBase = (file: File): Promise<unknown> => {
  return new Promise((resolve, reject) => {
    if (file == undefined) reject(null)
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = function () {
      const baseData: string | ArrayBuffer | null = this.result
      resolve(baseData)
    }
  })
}

// base64 -> file
const changeBaseToFile = (
  base64: string,
  fileName: string,
  type: string
): File => {
  let data = base64.split(',')[1]
  data = window.atob(data)
  const ia = new Uint8Array(data.length)
  for (let i = 0; i < data.length; i++) {
    ia[i] = data.charCodeAt(i)
  }
  const file = new File([ia], fileName + `.${type}`, {
    type: `image/${type}`
  })
  return file
}

export class CompressImg {
  preUploadOption: preUploadOption
  preUpload: preUpload

  constructor(preUploadOption: preUploadOption) {
    if (!preUploadOption.file) throw new Error('请传入文件')
    this.preUploadOption = preUploadOption
    const { type, name, size } = preUploadOption.file
    this.preUpload = {
      file: preUploadOption.file,
      fileName: name.split('.')[0],
      fileType: type.split('image/')[1],
      size: (size / (1024 * 1024)).toFixed(2),
      code: codeEnum.UNCOMPRESSED
    }
  }

  // 检查类型
  checkLimitType(): boolean {
    if (
      this.preUploadOption.limitType &&
      !this.preUploadOption.limitType.includes(this.preUpload.fileType)
    ) {
      this.preUpload.code = codeEnum.TYPE_ERROR
      return false
    }
    return true
  }

  // 检查大小  1.不需要压缩的时候， 2. 需要压缩但是压缩后的尺寸大于了限定尺寸
  checkLimitSize(
    limitSize: `${string}M` | undefined = this.preUploadOption.limitSize,
    size: string = this.preUpload.size
  ): boolean {
    let type
    if (limitSize) {
      type = limitSize.split('M')[0]
    }
    if (type && type < parseFloat(size)) {
      this.preUpload.code = codeEnum.SIZE_EXCEEDS
      return false
    }
    return true
  }

  // 换成需要的类型
  async changeFileType(
    newImageData: string,
    isChangeType = false
  ): Promise<void> {
    if (
      !this.preUploadOption.option?.fileType ||
      this.preUploadOption.option?.fileType === fileType.FILE
    ) {
      this.preUpload.file = await changeBaseToFile(
        newImageData,
        this.preUpload.fileName,
        this.preUpload.fileType
      )
    } else {
      this.preUpload.file = isChangeType
        ? newImageData.replace(
            `data:image/jpeg;base64,`,
            'data:image/png;base64,'
          )
        : newImageData
    }
  }

  // canvas 压缩
  getCanvasImage(
    image,
    targetWidth,
    targetHeight,
    quality = 0.92,
    fileType = this.preUpload.fileType
  ): Promise<string> {
    const that = this
    return new Promise<string>(async (resolve) => {
      const canvas: HTMLCanvasElement = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      canvas.width = targetWidth
      canvas.height = targetHeight

      ctx.fillStyle = '#fff'
      ctx?.fillRect(0, 0, canvas.width, canvas.height)

      ctx?.clearRect(0, 0, targetWidth, targetHeight)
      ctx?.drawImage(image, 0, 0, canvas.width, canvas.height)
      const newImageData: string = await canvas.toDataURL(
        `image/${fileType}`,
        quality
      )

      let str: string = newImageData.replace(
        `data:image/${fileType};base64,`,
        ''
      )
      const equalIndex: number = str.indexOf('=')
      if (str.indexOf('=') > 0) {
        str = str.substring(0, equalIndex)
      }
      const length: number = str.length
      that.preUpload.size = (
        (length - (length / 8) * 2) /
        (1024 * 1024)
      ).toFixed(2)
      resolve(newImageData)
    })
  }

  // 按照尺寸
  compressBySize(): Promise<preUpload> {
    const that = this
    return new Promise<preUpload>(async (resolve, reject) => {
      if (!that.checkLimitType()) reject(that.preUpload)
      if (that.preUploadOption?.option?.targetSize) {
        const option: Option = that.preUploadOption.option
        const targetSize = that.preUploadOption.option.targetSize
        if (
          !that.checkLimitSize(
            this.preUploadOption.limitSize,
            option?.targetSize
          )
        )
          reject(that.preUpload)
        const limitSize = option.maxSize
          ? option.maxSize.split('M')[0]
          : that.preUploadOption.limitSize
          ? that.preUploadOption.limitSize.split('M')[0]
          : null
        if (limitSize && that.preUpload.size > limitSize) {
          that.preUpload.code = codeEnum.COMPRESSION
          const file = (await changeFileToBase(
            that.preUploadOption.file
          )) as string
          const base = await that.changeImgSize(
            file,
            targetSize.split('M')[0],
            0
          )
          await that.changeFileType(base, that.preUpload.fileType === 'png')
        }
        if (!that.checkLimitSize(option?.targetSize))
          reject('压缩失败，未能压缩到需要大小')
        resolve(that.preUpload)
      } else reject('未传入压缩目标尺寸')
    })
  }
  changeImgSize(
    base: string,
    targetSize: string,
    scalcIndex: number
  ): Promise<string> {
    const that = this
    return new Promise<string>(async (resolve) => {
      const base64Image: unknown = base
      const image = new Image()
      image.src = base64Image as string
      if (that.preUpload.size > targetSize && scalcIndex <= 20) {
        image.onload = async function () {
          const width = image.width
          const height = image.height
          const type =
            that.preUpload.fileType === 'png' ? 'jpeg' : that.preUpload.fileType
          scalcIndex++
          const newImageData = await that.getCanvasImage(
            image,
            width,
            height,
            0.7,
            type
          )
          that
            .changeImgSize(newImageData, targetSize, scalcIndex)
            .then((res) => {
              resolve(res)
            })
        }
      } else {
        resolve(base)
      }
    })
  }

  // 按照宽高
  compress(): Promise<preUpload> {
    const that = this
    return new Promise<preUpload>((resolve, reject) => {
      if (!that.checkLimitType()) reject(that.preUpload)
      if (that.preUploadOption.option) {
        const option = that.preUploadOption.option
        if (!option?.targetWidth && !option?.targrtHeight)
          reject('请传入压缩后的宽或者高')

        changeFileToBase(that.preUploadOption.file).then(async (res) => {
          const base64Image: unknown = res
          const image = new Image()
          image.src = base64Image as string
          image.onload = async function () {
            const width = image.naturalWidth
            const height = image.naturalHeight

            let targetWidth =
              typeof option?.targetWidth === 'string'
                ? parseFloat(option.targetWidth.split('px')[0])
                : option?.targetWidth
            let targetHeight =
              typeof option?.targrtHeight === 'string'
                ? parseFloat(option?.targrtHeight.split('px')[0])
                : option?.targrtHeight

            if (!targetWidth) {
              targetWidth = (width / height) * (targetHeight as number)
            }

            if (!targetHeight) {
              targetHeight = targetWidth / (width / height)
            }

            if (width > targetWidth || height > targetHeight) {
              that.preUpload.code = codeEnum.COMPRESSION
              const newImageData = await that.getCanvasImage(
                image,
                targetWidth,
                targetHeight
              )
              await that.changeFileType(newImageData)
            }
            if (!that.checkLimitSize()) reject('压缩后大小超限')
            resolve(that.preUpload)
          }
        })
      } else reject('未传入压缩条件')
    })
  }
}
